﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Krile.Kernel.Data;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using System.IO;    //InteropServices をusingしてくださいませ

namespace Krile.Forms.Dialogs.Editor.SearchData
{
    public partial class ClusterEditor : Form
    {
        private SearchDataCluster _sdc = null;
        public SearchDataCluster SearchDataCluster
        {
            get { return _sdc; }
            set
            {
                _sdc = value;
                GenerateSDList();
            }
        }

        class SDBaseTreeNode : TreeNode
        {
            public SDBaseTreeNode(SearchDataBase item)
                : base(item.ToString())
            {
                LinkedItem = item;
                this.ImageIndex = item is SearchDataCluster ? 0 : 1;
                this.SelectedImageIndex = this.ImageIndex;
            }

            public SearchDataBase LinkedItem = null;

            public void DoubleClickCallback()
            {
                if (LinkedItem != null)
                {
                    var cls = LinkedItem as SearchDataCluster;
                    if (cls != null)
                    {
                        if (!cls.UseOrMatch)
                        {
                            cls.UseOrMatch = true;
                        }
                        else if (!cls.Exclude)
                        {
                            cls.UseOrMatch = false;
                            cls.Exclude = true;
                        }
                        else
                        {
                            cls.UseOrMatch = false;
                            cls.Exclude = false;
                        }
                    }
                    else
                    {
                        using (var sed = new SearchDataEditor())
                        {
                            sed.SelectedData = LinkedItem as Krile.Kernel.Data.SearchData;
                            if (sed.ShowDialog() == DialogResult.OK)
                                this.LinkedItem = sed.SelectedData;
                        }
                    }
                }
            }

            public bool AppendableChildren { get { return LinkedItem is SearchDataCluster; } }
        }

        public ClusterEditor() : this(new SearchDataCluster()) { }

        public ClusterEditor(SearchDataCluster cluster)
        {
            InitializeComponent();
            this.SearchDataCluster = cluster;
        }

        private void GenerateSDList()
        {
            sdList.Nodes.Clear();
            if (SearchDataCluster != null && SearchDataCluster.SearchDatas != null)
            {
                sdList.Nodes.Add(GenerateSDListCo(SearchDataCluster));
            }
            else
            {
                sdList.Nodes.Add(new SDBaseTreeNode(new SearchDataCluster()));
            }
            sdList.ExpandAll();
        }

        private TreeNode GenerateSDListCo(SearchDataBase sdb)
        {
            if (sdb == null)
                return null;
            SDBaseTreeNode tn = new SDBaseTreeNode(sdb);
            var sdc = sdb as SearchDataCluster;
            if (sdc != null && sdc.SearchDatas != null)
            {
                foreach (var i in sdc.SearchDatas)
                {
                    tn.Nodes.Add(GenerateSDListCo(i));
                }
                return tn;
            }
            else
                return tn;
        }

        private void okBtn_Click(object sender, EventArgs e)
        {
            this._sdc = GenerateSDBCo(sdList.Nodes[0]) as SearchDataCluster;
            DialogResult = DialogResult.OK;
            this.Close();
        }

        private SearchDataBase GenerateSDBCo(TreeNode node)
        {
            var sdbn = node as SDBaseTreeNode;
            if (sdbn == null)
                return null;
            var cluster = sdbn.LinkedItem as SearchDataCluster;
            if (cluster != null)
            {
                var bases = new List<SearchDataBase>();
                foreach (var d in sdbn.Nodes)
                {
                    var ret = GenerateSDBCo(d as TreeNode);
                    if (ret != null)
                        bases.Add(ret);
                }
                var nc = new SearchDataCluster(bases.ToArray()) { UseOrMatch = cluster.UseOrMatch, Exclude = cluster.Exclude };
                return nc;
            }
            else
            {
                return sdbn.LinkedItem;
            }
        }

        private void sdList_AfterSelect(object sender, TreeViewEventArgs e)
        {
            cutBtn.Enabled = deleteBtn.Enabled = sdList.SelectedNode != null && sdList.SelectedNode != sdList.Nodes[0];
            copyBtn.Enabled = sdList.SelectedNode != null;
            addClusterBtn.Enabled = false;
            addDataBtn.Enabled = false;
            var sdbn = sdList.SelectedNode as SDBaseTreeNode;
            if (sdbn != null)
            {
                if (sdbn.LinkedItem is SearchDataCluster)
                {
                    addClusterBtn.Enabled = true;
                    addDataBtn.Enabled = true;
                    this.helpText.Text = Lang.Msg.SDEdit_Help_Head + Lang.Msg.SDEdit_Help_Cluster;
                    pasteBtn.Enabled = true;
                }
                else
                {
                    this.helpText.Text = Lang.Msg.SDEdit_Help_Head + Lang.Msg.SDEdit_Help_Data;
                    pasteBtn.Enabled = false;
                }
            }
            else
            {
                this.helpText.Text = Lang.Msg.SDEdit_Help_Head + Lang.Msg.SDEdit_Help_Standby;
            }
        }

        private void addClusterBtn_Click(object sender, EventArgs e)
        {
            if (sdList.SelectedNode != null)
            {
                var target = sdList.SelectedNode;
                var newNode = new SDBaseTreeNode(new SearchDataCluster());
                target.Nodes.Add(newNode);
                sdList.SelectedNode = newNode;
            }
        }

        private void addDataBtn_Click(object sender, EventArgs e)
        {
            if (sdList.SelectedNode != null)
            {
                var target = sdList.SelectedNode;
                using (var sde = new SearchDataEditor())
                {
                    if (sde.ShowDialog() == DialogResult.OK)
                    {
                        var newNode = new SDBaseTreeNode(sde.SelectedData);
                        target.Nodes.Add(newNode);
                        sdList.SelectedNode = newNode;
                    }
                }
            }

        }

        private void deleteBtn_Click(object sender, EventArgs e)
        {
            if (sdList.SelectedNode != sdList.Nodes[0] && sdList.SelectedNode != null)
                sdList.Nodes.Remove(sdList.SelectedNode);
        }

        private void sdList_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                deleteBtn_Click(sender, e);
            }
            else if (e.Control)
            {
                switch (e.KeyCode)
                {
                    case Keys.C:
                        CopyNode();
                        break;
                    case Keys.X:
                        CutNode();
                        break;
                    case Keys.V:
                        PasteNode();
                        break;
                }
            }
        }

        private void sdList_MouseClick(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Right)
            {
                var sdbt = sdList.SelectedNode as SDBaseTreeNode;
                if (sdbt != null)
                {
                    sdbt.DoubleClickCallback();
                    sdbt.Text = sdbt.LinkedItem.ToString();
                }
            }
        }

        private void copyBtn_Click(object sender, EventArgs e)
        {
            CopyNode();
        }

        private void CopyNode()
        {
            if (sdList.SelectedNode != null)
            {
                var sdbn = sdList.SelectedNode as SDBaseTreeNode;
                if (sdbn == null) return;
                var wrapper = new SearchDataCluster(new[] { sdbn.LinkedItem });
                using(var ms = new MemoryStream())
                {
                    XmlSerializer xs = new XmlSerializer(typeof(SearchDataCluster));
                    xs.Serialize(ms, wrapper);
                    ms.Seek(0, SeekOrigin.Begin);
                    using (var sr = new StreamReader(ms))
                    {
                        Clipboard.SetText(sr.ReadToEnd());
                    }
                }
            }
        }

        private void cutBtn_Click(object sender, EventArgs e)
        {
            CutNode();
        }

        private void CutNode()
        {
            if (sdList.SelectedNode != sdList.Nodes[0] && sdList.SelectedNode != null)
            {
                CopyNode();
                sdList.Nodes.Remove(sdList.SelectedNode);
            }
            else if (sdList.SelectedNode == sdList.Nodes[0])
            {
                MessageBox.Show("Root element can't remove.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            }
        }

        private void pasteBtn_Click(object sender, EventArgs e)
        {
            PasteNode();
        }

        private void PasteNode()
        {
            if (sdList.SelectedNode != null && Clipboard.ContainsText())
            {
                var sdbn = sdList.SelectedNode as SDBaseTreeNode;
                if (sdbn == null) return;
                var sdc = sdbn.LinkedItem as SearchDataCluster;
                if (sdc == null) return;
                using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(Clipboard.GetText())))
                {
                    try
                    {
                        XmlSerializer xs = new XmlSerializer(typeof(SearchDataCluster));
                        var o = xs.Deserialize(ms) as SearchDataCluster;
                        if (o == null || o.SearchDatas == null) return;
                        foreach (var s in o.SearchDatas)
                            AddRecursive(sdbn, s);
                    }
                    catch { }
                }
            }
        }

        private void AddRecursive(TreeNode target, SearchDataBase data)
        {
            var newNode = new SDBaseTreeNode(data);
            target.Nodes.Add(newNode);
            sdList.SelectedNode = newNode;

            var cluster = data as SearchDataCluster;
            if (cluster != null)
            {
                foreach (var i in cluster.SearchDatas)
                {
                    AddRecursive(newNode, i);
                }
            }
        }

        //code from:TreeViewのノードをDrag&Dropにより移動、コピーできるようにする - DOBON.NET
        //http://dobon.net/vb/dotnet/control/tvdraganddrop.html

        private void sdList_ItemDrag(object sender, ItemDragEventArgs e)
        {
            TreeView tv = (TreeView)sender;
            tv.SelectedNode = (TreeNode)e.Item;
            tv.Focus();
            //ノードのドラッグを開始する
            DragDropEffects dde =
                tv.DoDragDrop(e.Item, DragDropEffects.All);
        }

        private void sdList_DragOver(object sender, DragEventArgs e)
        {
            //ドラッグされているデータがTreeNodeか調べる
            if (e.Data.GetDataPresent(typeof(SDBaseTreeNode)))
            {
                if ((e.KeyState & 8) == 8 &&
                    (e.AllowedEffect & DragDropEffects.Copy) ==
                    DragDropEffects.Copy)
                    //Ctrlキーが押されていればCopy
                    //"8"はCtrlキーを表す
                    e.Effect = DragDropEffects.Copy;
                else if ((e.AllowedEffect & DragDropEffects.Move) ==
                    DragDropEffects.Move)
                    //何も押されていなければMove
                    e.Effect = DragDropEffects.Move;
                else
                    e.Effect = DragDropEffects.None;
            }
            else
                //TreeNodeでなければ受け入れない
                e.Effect = DragDropEffects.None;

            //マウス下のNodeを選択する
            if (e.Effect != DragDropEffects.None)
            {
                TreeView tv = (TreeView)sender;
                var vp = tv.PointToClient(new Point(e.X, e.Y)).Y;
                int limh = tv.Height > 10 ? tv.Height / 10 : 1;

                if (vp <= limh)
                {
                    //↑スクロール
                    ScrollTreeView(tv, -5);
                }
                else if (tv.Height - vp <= limh)
                {
                    //↓スクロール
                    ScrollTreeView(tv, 5);
                }

                //マウスのあるNodeを取得する
                var target = tv.GetNodeAt(tv.PointToClient(new Point(e.X, e.Y))) as SDBaseTreeNode;
                //ドラッグされているNodeを取得する
                var source = (SDBaseTreeNode)e.Data.GetData(typeof(SDBaseTreeNode));
                //マウス下のNodeがドロップ先として適切か調べる
                if (target != null && target != source && target.AppendableChildren &&
                    !IsChildNode(source, target))
                {
                    //Nodeを選択する
                    if (target.IsSelected == false)
                        tv.SelectedNode = target;
                }
                else
                    e.Effect = DragDropEffects.None;
            }
        }

        #region  Win32APIのSendMessage()を使って，コンポーネントに対しメッセージを直接投げつけます
        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        public static extern Int32 SendMessage(IntPtr hWnd, Int32 Msg, int wParam, int lParam);
        #endregion

        //メッセージ関係の定数を定義
        private const int WM_VSCROLL = 0x115;						  //WM_VSCROLLメッセージを使用します
        private const int SB_LINEUP = 0;	  //上矢印を押した
        private const int SB_LINEDOWN = 1;	  //下矢印を押した
        private const int SB_PAGEUP = 2;	  //上ページアップを押した
        private const int SB_PAGEDOWN = 3;	  //下ページアップを押した
        /// <summary>
        /// ツリービューをスクロールします。
        /// </summary>
        /// <param name="tv">対象ツリービュー</param>
        /// <param name="direction">移動方向(～-1:↑ 1～:↓)</param>
        private void ScrollTreeView(TreeView tv, int direction)
        {
            tv.BeginUpdate();
            if (direction > 0)
            {
                for (var i = 0; i < direction; i++)
                {
                    SendMessage(tv.Handle, WM_VSCROLL, SB_LINEDOWN, 0);
                }
            }
            else
            {
                for (var i = 0; i > direction; i--)
                {
                    SendMessage(tv.Handle, WM_VSCROLL, SB_LINEUP, 0);
                }
            }
            tv.EndUpdate();
        }

        private void sdList_DragDrop(object sender, DragEventArgs e)
        {
            //ドロップされたデータがTreeNodeか調べる
            if (e.Data.GetDataPresent(typeof(SDBaseTreeNode)))
            {
                TreeView tv = (TreeView)sender;
                //ドロップされたデータ(TreeNode)を取得
                var source = (SDBaseTreeNode)e.Data.GetData(typeof(SDBaseTreeNode));
                //ドロップ先のTreeNodeを取得する
                var target = tv.GetNodeAt(tv.PointToClient(new Point(e.X, e.Y))) as SDBaseTreeNode;
                if ((e.Effect & DragDropEffects.Move) == DragDropEffects.Move)
                    sdList.Nodes.Remove(source);
                //マウス下のNodeがドロップ先として適切か調べる
                if (target != null && target != source && target.AppendableChildren &&
                    !IsChildNode(source, target))
                {
                    //ドロップされたNodeのコピーを作成
                    //Nodeを追加
                    target.Nodes.Add(source);
                    //ドロップ先のNodeを展開
                    target.Expand();
                    //追加されたNodeを選択
                    tv.SelectedNode = source;

                }
                else
                    e.Effect = DragDropEffects.None;
            }
            else
                e.Effect = DragDropEffects.None;
        }

        /// <summary>
        /// あるTreeNodeが別のTreeNodeの子ノードか調べる
        /// </summary>
        /// <param name="parent">親ノードか調べるTreeNode</param>
        /// <param name="child">子ノードか調べるTreeNode</param>
        /// <returns>子ノードの時はTrue</returns>
        private static bool IsChildNode(TreeNode parent, TreeNode child)
        {
            if (child.Parent == parent)
                return true;
            else if (child.Parent != null)
                return IsChildNode(parent, child.Parent);
            else
                return false;
        }

        private void queryButton_Click(object sender, EventArgs e)
        {
            using(var kq = new QueryEditor())
            {
                kq.Cluster = GenerateSDBCo(sdList.Nodes[0]) as SearchDataCluster;
                if (kq.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                {
                    _sdc = kq.Cluster;
                    GenerateSDList();
                }
            }
        }

    }
}
